package org.elsfs.auth.client;

import static org.assertj.core.api.Assertions.assertThat;

import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import java.security.interfaces.RSAPrivateKey;
import java.util.Date;
import org.elsfs.auth.entity.ResponseBody;
import org.elsfs.framework.generator.SnowFlake;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

/** private_key_jwt */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class PRIVATE_KEY_JWT extends AbstractClientTypeAuthorizationServerTests {
  private final String CLIENT_ID = "test_private_key_jwt";
  private final String CLIENT_SECRET = "secret";
  private final String CLIENT_SECRET_ENCODE =
      PasswordEncoderFactories.createDelegatingPasswordEncoder().encode(CLIENT_SECRET);

  @Value("${elsfs.security.private-key-path}")
  RSAPrivateKey privateKey;

  @Override
  @Test
  @Order(50)
  void exchange() {
    // addRegisteredClient();
    MultiValueMap<String, Object> requestMap = new LinkedMultiValueMap<>();
    requestMap.add(
        OAuth2ParameterNames.CLIENT_ASSERTION_TYPE,
        "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
    requestMap.add(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID);
    requestMap.add(OAuth2ParameterNames.SCOPE, "openid profile");
    requestMap.add(OAuth2ParameterNames.CLIENT_ASSERTION, createPrivateKeyJwtToken());
    requestMap.add(OAuth2ParameterNames.GRANT_TYPE, "client_credentials");

    RequestEntity<MultiValueMap<String, Object>> request =
        RequestEntity.post("http://localhost:5001/oauth2/token")
            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
            .accept(MediaType.APPLICATION_JSON)
            .body(requestMap);
    // 3.响应体
    ResponseEntity<ResponseBody> response = restTemplate.exchange(request, ResponseBody.class);
    assertThat(response.getBody()).isNotNull();
  }

  private void addRegisteredClient() {
    RegisteredClient registeredClient =
        RegisteredClient.withId(new SnowFlake(31).nextId() + "")
            .clientId(CLIENT_ID)
            .clientSecret(CLIENT_SECRET_ENCODE)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
            .scope(OidcScopes.OPENID)
            .scope(OidcScopes.PROFILE)
            .clientSettings(
                ClientSettings.builder()
                    .tokenEndpointAuthenticationSigningAlgorithm(
                        SignatureAlgorithm.RS256) // private_key_jwt需要 断言对应的算法
                    .jwkSetUrl("http://localhost:5001/oauth2/jwks") // private_key_jwt 需要
                    .build())
            .tokenSettings(TokenSettings.builder().build())
            .build();
    registeredClientRepository.save(registeredClient);
  }

  private String createPrivateKeyJwtToken() {
    // 至少以下四项信息
    JWTClaimsSet claimsSet =
        new JWTClaimsSet.Builder()
            // 主体：固定clientId
            .subject(CLIENT_ID)
            // 发行者：固定clientId
            .issuer(CLIENT_ID)
            // 授权中心的地址
            .audience(authorizationServerSettings.getIssuer())
            // 过期时间 24h
            .expirationTime(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24))
            .build();

    SignedJWT signedJWT = null;
    try {
      JWSHeader header = new JWSHeader(JWSAlgorithm.RS256);
      RSASSASigner signer = new RSASSASigner(privateKey);
      String s = signer.toString();
      signedJWT = new SignedJWT(header, claimsSet);
      signedJWT.sign(signer);

    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    String token = signedJWT.serialize();
    System.out.println(token);
    return token;
  }
}
